﻿using System;
using System.Configuration;
using Reactor.Providers;
using Samurai.Wakizashi.Caching;

namespace Reactor.Configuration
{
    public class SqlConfigurationStore : IConfigurationStore
    {
        #region Fields

        private readonly IConfigurationDataProvider _dataProvider;
        private readonly ExpiringKeyedCache _configurationElements = new ExpiringKeyedCache(TimeSpan.FromMinutes(10));
        private bool _elementsCached;

        #endregion
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SqlConfigurationStore"/> class.
        /// </summary>
        /// <param name="dataProvider">The data provider used for global long-term configuration values.</param>
        public SqlConfigurationStore(IConfigurationDataProvider dataProvider)
        {
            if (dataProvider == null) throw new ArgumentNullException("dataProvider");

            _configurationElements.ExpireAction = OnCacheExpired;
            _dataProvider = dataProvider;
        }

        /// <summary>
        /// Loads configuration elements from long term storage. 
        /// <remarks>Uses the <seealso cref="IConfigurationDataProvider"/>  provided in the constructor to obtain these elements.</remarks>
        /// </summary>
        public void LoadConfigurationElementsFromLongTermStorage()
        {
            var elements = _dataProvider.GetAllElements();
            if(elements.Length == 0) return;

            foreach (var configurationElement in elements)
                _configurationElements.CacheItem(configurationElement.Key, configurationElement.Value);

            _elementsCached = true;
        }

        /// <summary>
        /// Stores the configuration value at the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public void StoreConfigurationValue(string key, string value)
        {
            _dataProvider.StoreConfigurationValue(key, value);
        }

        /// <summary>
        /// Gets the configuration value at the specified key.
        /// <remarks>This method first searches application configuration appsettings. If none is found, the 
        /// configuration values obtained remotely are searched.</remarks>
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The string representation of the value found at the specified key.</returns>
        public string GetConfigurationValue(string key)
        {
            var appSettingValue = ConfigurationManager.AppSettings[key]; 

            // Look locally first
            if(!string.IsNullOrEmpty(appSettingValue))
                return appSettingValue;

            // Obtain items if necessary
            if (!_elementsCached) LoadConfigurationElementsFromLongTermStorage();

            // Attempt to return from remote configuration
            return _configurationElements.GetItemAs<string>(key);
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            _configurationElements.Clear();
        }

        private void OnCacheExpired(IExpiringCacheStore cacheStore)
        {
            LoadConfigurationElementsFromLongTermStorage();
        }
    }
}